package crmdna.client.programtype;

import static crmdna.common.OfyService.ofy;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.Work;

import crmdna.client.practice.Practice;
import crmdna.client.practice.Practice.PracticeProp;
import crmdna.client.practice.PracticeEntity;
import crmdna.client.sequence.Sequence;
import crmdna.client.sequence.Sequence.SequenceType;
import crmdna.client.user.User;
import crmdna.client.user.User.Action;
import crmdna.client.user.User.ResourceType;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.Utils;
import crmdna.crmdna.Client;

public class ProgramType {	
	public static class ProgramTypeProp {
		public long programTypeId;
		public String name;
		public String displayName;	
		public List<PracticeProp> practiceProps = new ArrayList<>();
	}
	
	public static ProgramTypeProp create(String client, String displayName, Set<Long> practiceIds, 
			String login) {
		
		Client.ensureValidClient(client);
		User.ensureAccess(client, login, ResourceType.PROGRAM_TYPE, 0, Action.WRITE);
		
		String name = displayName.toLowerCase();
		
		List<Key<ProgramTypeEntity>> keys = ofy(client).load()
				.type(ProgramTypeEntity.class).filter("name", name).keys().list();
		
		if (keys.size() != 0)
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, 
					"There is already a program type with name [" + displayName + "]");
			
		String key = getUniqueKey(client, name);
		long val = MemcacheServiceFactory.getMemcacheService()
				.increment(key, 1, (long)0);
		
		if (val != 1)
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, 
					"There is already a program type with name [" + displayName + "]");
		
		ProgramTypeEntity entity = new ProgramTypeEntity();		
		entity.programTypeId = Sequence.getNext(client, SequenceType.PROGRAM_TYPE);
		entity.name = name;
		entity.displayName = displayName;
		
		if (practiceIds != null) {
			for (Long practiceId : practiceIds) {
				PracticeEntity practiceEntity = Practice.safeGet(client, practiceId);
				entity.practiceEntities.add(Ref.create(practiceEntity));
			}			
		}
		
		ofy(client).save().entity(entity).now();		
		return entity.toProp();		
	}
	
	private static String getUniqueKey(String namespace, String name) {
		return namespace + "_" + SequenceType.PROGRAM_TYPE + "_" + name;
 	}
	
	public static ProgramTypeEntity safeGet(String client, long programTypeId) {
		
		Client.ensureValidClient(client);
		
		ProgramTypeEntity entity = ofy(client).load().type(ProgramTypeEntity.class).id(programTypeId).get();
		if (null == entity)
			throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST, 
					"Program type [" + programTypeId + "] does not exist");
		return entity;				
	}
	
	public static void ensureValid(String client, Set<Long> programTypeIds) {
		
		Client.ensureValidClient(client);
		
		Map<Long, ProgramTypeEntity> map = 
				ofy(client).load().type(ProgramTypeEntity.class).ids(programTypeIds);
		
		if (map.size() == programTypeIds.size())
			return; //all ids are valid
		
		for (Long id : programTypeIds) {
			if (! map.containsKey(id))
				Utils.throwNotFoundException("programTypeId [" + id + "] does not exist");
		}					
	}
			
	public static List<ProgramTypeProp> getAll(String client) {
		Client.ensureValidClient(client);
		
		List<ProgramTypeEntity> entities = ofy(client).load().type(ProgramTypeEntity.class)
				.order("name").list();
				
		List<ProgramTypeProp> props = new ArrayList<>();
		for (ProgramTypeEntity entity : entities) 
			props.add(entity.toProp());
		
		return props;							
	}
	
	public static List<Long> getPracticeIds(String client, long programTypeId) {
		Client.ensureValidClient(client);
		
		ProgramTypeProp programTypeProp = safeGet(client, programTypeId).toProp();
		
		List<Long> practiceIds = new ArrayList<>();
		for (PracticeProp practiceProp : programTypeProp.practiceProps) {
			practiceIds.add(practiceProp.practiceId);
		}
		
		return practiceIds;									
	}		
	
	public static Set<Long> getPracticeIds(String client, Set<Long> programTypeIds) {
		Client.ensureValidClient(client);
		
		Map<Long, ProgramTypeEntity> map = ofy(client).load().type(ProgramTypeEntity.class)
				.ids(programTypeIds);
		
		Set<Long> practiceIds = new HashSet<>();
		for (Long programTypeId : map.keySet()) {
			ProgramTypeEntity entity = map.get(programTypeId);
			for (Ref<PracticeEntity> pe : entity.practiceEntities) {
				practiceIds.add(pe.get().toProp().practiceId);
			}
		}
		
		return practiceIds;											
	}
	
	public static ProgramTypeProp rename(final String client, final long programTypeId, 
			final String newDisplayName, String login) {
		
		Client.ensureValidClient(client);
		User.ensureAccess(client, login, ResourceType.PROGRAM_TYPE, 0, Action.WRITE);
		
		ProgramTypeEntity entity = safeGet(client, programTypeId);
		
		final String newName = newDisplayName.toLowerCase();				
		
		//ensure unique if it not a simple upper case/lower case conversion
		if (! entity.name.equals(newName)) {
			List<Key<ProgramTypeEntity>> keys = ofy(client).load()
					.type(ProgramTypeEntity.class).filter("name", newName).keys().list();		
			if (keys.size() != 0)
				throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, 
						"There is already a program typee with name [" + newDisplayName + "]");
			
			String key = getUniqueKey(client, newName);
			long val = MemcacheServiceFactory.getMemcacheService()
					.increment(key, 1, (long)0);
			
			if (val != 1)
				throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, 
						"There is already a program type with name [" + newDisplayName + "]");
		}
		
		ProgramTypeProp prop = ofy(client).transact(new Work<ProgramTypeProp>() {

			@Override
			public ProgramTypeProp run() {
				ProgramTypeEntity entity = safeGet(client, programTypeId);
				entity.name = newName;
				entity.displayName = newDisplayName;
				ofy(client).save().entity(entity).now();
				
				return entity.toProp();				
			}
		});
				
		return prop;		
	}		
			
	public static void delete(String client, long programTypeId, String login) {
						
		Client.ensureValidClient(client);
		User.ensureAccess(client, login, ResourceType.PROGRAM_TYPE, 0, Action.WRITE);
		
		throw new APIException(Status.ERROR_NOT_IMPLEMENTED, 
				"Functionality to delete program type not yet implemented");		
	}
}
